// SPDX-License-Identifier: GPL-2.0
/*
 * BiscuitOS GUP AND CONTIGOUS MEMORY
 *
 * (C) 2024.04.10 BuddyZhang1 <buddy.zhang@aliyun.com>
 * (C) 2024.04.10 BiscuitOS
 */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/dma-map-ops.h>

#define DEV_NAME		"BiscuitOS"

static struct BiscuitOS_device {
	struct platform_device *pdev;
	/* DMA Memory Physical Address */
	dma_addr_t dma_addr;
	/* DMA Memory Virtual Address */
	char *dma_buffer;
} bdev;

static ssize_t BiscuitOS_write(struct file *filp, const char __user *buf,
			size_t len, loff_t *offset)
{
	unsigned long addr = (unsigned long)buf;
	struct page *page;

	get_user_pages(addr, 1, FOLL_GET, &page, NULL);

	if (!page) {
		printk("GUP FAILED.\n");
	} else
		printk("DST PFN %#lx\n", page_to_pfn(page));

	return len;
}

static int BiscuitOS_mmap(struct file *filp, struct vm_area_struct *vma)
{
	size_t size = vma->vm_end - vma->vm_start;
	unsigned long addr;
	struct page *page;

	/* FORCE DMA FROM CMA */
	bdev.pdev->dev.dma_ops = NULL;

	/* ALLOC CONTIGOUS MEMORY */
	bdev.dma_buffer = dma_alloc_coherent(&bdev.pdev->dev,
				size, &bdev.dma_addr, GFP_KERNEL);
	if (!bdev.dma_buffer) {
		printk("SYSTEM ERROR: CMA ALLOC FAILED.\n");
		return -ENOMEM;
	}

	/* PHYS TO PAGE */
	page = pfn_to_page(bdev.dma_addr >> PAGE_SHIFT);
	printk("SRC PFN %#lx\n", page_to_pfn(page));

	/* CREATE PGTABLE */
	for (addr = vma->vm_start; addr <= vma->vm_end; addr += PAGE_SIZE) {
		vm_insert_page(vma, addr, page);
		page++;
	}

	return 0;
}

static int BiscuitOS_probe(struct platform_device *pdev)
{
	return 0;
}

static int BiscuitOS_remove(struct platform_device *pdev)
{
	return 0;
}

static struct file_operations BiscuitOS_fops = {
	.owner		= THIS_MODULE,
	.write		= BiscuitOS_write,
	.mmap		= BiscuitOS_mmap,
};

static struct miscdevice BiscuitOS_drv = {
	.minor	= MISC_DYNAMIC_MINOR,
	.name	= DEV_NAME,
	.fops	= &BiscuitOS_fops,
};

static struct platform_driver BiscuitOS_driver = {
	.probe    = BiscuitOS_probe,
	.remove   = BiscuitOS_remove,
	.driver = {
		.owner  = THIS_MODULE,
		.name   = DEV_NAME,
	},
};

static int __init BiscuitOS_init(void)
{
	int ret;

	ret = platform_driver_register(&BiscuitOS_driver);
	if (ret) {
		printk("Error: Platform driver register.\n");
		return -EBUSY;
	}

	bdev.pdev = platform_device_register_simple(DEV_NAME, 1, NULL, 0);
	if (IS_ERR(bdev.pdev)) {
		printk("Error: Platform device register\n");
		return PTR_ERR(bdev.pdev);
	}

	misc_register(&BiscuitOS_drv);
	return 0;
}

static void __exit BiscuitOS_exit(void)
{
	misc_deregister(&BiscuitOS_drv);
	platform_device_unregister(bdev.pdev);
	platform_driver_unregister(&BiscuitOS_driver);
}

module_init(BiscuitOS_init);
module_exit(BiscuitOS_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("BiscuitOS <buddy.zhang@aliyun.com>");
MODULE_DESCRIPTION("BiscuitOS MISC Device Driver");
